from binascii import b2a_hex, a2b_hex
import rsa
from Crypto.Cipher.AES import MODE_CBC, MODE_ECB
from functools import wraps
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
import hashlib
import urllib.parse
import base64
import re
import time
import hmac
import hashlib
from datetime import datetime


def retry():
    def outter(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                result = func(*args, **kwargs)
                return result
            except Exception as e:
                return None

        return wrapper

    return outter


@retry()
def base64_encode(text, encoding='utf-8'):
    text_bytes = text.encode(encoding)

    # 使用 Base64 编码将字节数据转换为字符串
    encoded_bytes = base64.b64encode(text_bytes)

    # 将字节类型转换为字符串类型
    encoded_string = encoded_bytes.decode(encoding)

    return encoded_string


@retry()
def base64_decode(encoded_string, encoding='utf-8'):
    # 将字符串转换为字节类型
    encoded_bytes = encoded_string.encode(encoding)

    # 使用 Base64 解码将字节数据转换为原始字节数据
    decoded_bytes = base64.b64decode(encoded_bytes)

    # 将字节类型转换为字符串类型
    decoded_string = decoded_bytes.decode(encoding)

    return decoded_string


@retry()
def md5_hash(data):
    # 创建MD5哈希对象
    md5 = hashlib.md5()
    # 更新哈希对象的输入数据（需要将字符串编码为字节型）
    md5.update(data.encode('utf-8'))
    # 计算并返回哈希值（以十六进制字符串表示）
    return md5.hexdigest()


@retry()
def aes_encrypt(key, plaintext, iv, model='cbc', padding='pkcs7'):
    # 生成随机的初始向量（IV）
    key = key.encode('utf-8')
    iv = iv.encode('utf-8')
    plaintext = plaintext.encode('utf-8')
    # 确保密钥长度是 16 字节

    # 创建 AES 加密器对象
    if model == "cbc":
        cipher = AES.new(key, AES.MODE_CBC, iv)
    elif model == "ecb":
        cipher = AES.new(key, AES.MODE_ECB)

    # 使用 PKCS7 填充方式对明文进行填充
    if padding == 'zeropadding':
        padded_plaintext = zero_pad(plaintext, AES.block_size)
    elif padding == 'pkcs7':
        padded_plaintext = pad(plaintext, AES.block_size)

    ciphertext = cipher.encrypt(padded_plaintext)

    # 执行加密操作
    ciphertext_str = base64.b64encode(ciphertext).decode('utf-8')

    # 返回加密后的密文和初始向量（IV）
    return ciphertext_str


@retry()
def aes_decrypt(key, ciphertext, iv, model='cbc', padding='pkcs7'):
    key = key.encode('utf-8')
    iv = iv.encode('utf-8')
    ciphertext = ciphertext.encode('utf-8')
    # 使用 PKCS7 填充方式对明文进行填充
    if padding == 'zeropadding':
        padded_plaintext = zero_pad(ciphertext, AES.block_size)
    elif padding == 'pkcs7':
        padded_plaintext = pad(ciphertext, AES.block_size)

    # 将 Base64 编码的密文转换为字节类型
    ciphertext_bytes = base64.b64decode(padded_plaintext)

    if model == "cbc":
        cipher = AES.new(key, AES.MODE_CBC, iv)
    elif model == "ecb":
        cipher = AES.new(key, AES.MODE_ECB)

    # 执行解密操作
    plaintext_bytes = cipher.decrypt(ciphertext_bytes)
    # 将明文字节数据转换为字符串
    plaintext_str = plaintext_bytes.rstrip(
        b'\x00').rstrip(b'\x08').decode('utf-8')

    # 返回解密后的明文
    return plaintext_str


def zero_pad(data, block_size):
    padding_size = block_size - (len(data) % block_size)
    padded_data = data + b'\x00' * padding_size
    return padded_data


def sha1(word=None):
    m = hashlib.sha1()
    m.update(str(word).encode("utf-8"))
    return m.hexdigest()


def sha256(word=None):
    sha256 = hashlib.sha256()
    sha256.update(word.encode())
    res = sha256.hexdigest()
    return res


def sha512(word=None):
    the_sha512 = hashlib.sha512()
    the_sha512.update(word.encode('utf-8'))
    the_string_sha1 = the_sha512.hexdigest()
    return the_string_sha1


@retry()
def unicode_escape(word=None):
    word = word.replace('\\\\', '\\')
    if isinstance(word, bytes):
        word = str(word, 'unicode_escape')
        word = bytes(word, 'unicode_escape')
        return word.decode('unicode_escape')
    if isinstance(word, str):
        word = re.sub(r'(\\u[a-zA-Z0-9]{4})', lambda x: x.group(
            1).encode("utf-8").decode("unicode-escape"), word)
        word = bytes(word, 'unicode_escape')
        return word.decode('unicode_escape')


def unicode(word=None):
    word = word.encode('unicode_escape').decode("utf-8")
    return word


def timestamp():
    return int(time.time() * 1000)


def d_timestamp(word=None):
    try:
        word = str(word)
        length = len(word)
        if length > 10:
            length = int(length) - 10
            for i in range(length):
                word = int(word) / 10
        else:
            word = int(word)

            # 格式化日期时间字符串
        now = time.localtime(word)
        now = time.strftime('%Y-%m-%d %H:%M:%S', now)
        return now
    except:
        return None


def url_unquote(word=None):
    # URL解码
    new_txt = urllib.parse.unquote(word, 'utf-8')
    return new_txt


def url_enquote(word=None):
    # URL解码
    new_txt = urllib.parse.quote(word, 'utf-8')
    return new_txt


def get_datetime_now(rule='%Y-%m-%d %H:%M:%S'):
    daytime = datetime.now()
    daytime = daytime.strftime(rule)
    return daytime


def hmac_md5(key, message):
    # 创建 HMAC-MD5 对象
    hmac_md5_obj = hmac.new(key.encode(), message.encode(), hashlib.md5)

    # 计算哈希值
    digest = hmac_md5_obj.digest()

    # 将哈希值转换为十六进制字符串
    hex_digest = digest.hex()

    return hex_digest


def hmac_encrypt(key, message, sha_type="sha256"):
    # 将密钥和消息转换为字节类型
    key_bytes = key.encode('utf-8')
    message_bytes = message.encode('utf-8')

    # 使用SHA256哈希算法创建HMAC对象
    if sha_type == "sha1":
        hmac_obj = hmac.new(key_bytes, message_bytes, hashlib.sha1)
    if sha_type == "sha3":
        hmac_obj = hmac.new(key_bytes, message_bytes, hashlib.sha3_256)
    if sha_type == "sha224":
        hmac_obj = hmac.new(key_bytes, message_bytes, hashlib.sha224)
    if sha_type == "sha256":
        hmac_obj = hmac.new(key_bytes, message_bytes, hashlib.sha256)
    if sha_type == "sha384":
        hmac_obj = hmac.new(key_bytes, message_bytes, hashlib.sha384)
    if sha_type == "sha512":
        hmac_obj = hmac.new(key_bytes, message_bytes, hashlib.sha512)

    # 计算HMAC摘要并返回结果
    digest = hmac_obj.digest()

    # 将摘要转换为十六进制表示的字符串
    result = digest.hex()

    return result


'''根据model、exponent加密, 无密钥'''


class Encrypt(object):
    def __init__(self, e, m):
        self.e = e if len(e) > 0 else '010001'
        self.m = m

    @retry()
    def encrypt(self, message):
        try:
            mm = int(self.m, 16)
            ee = int(self.e, 16)
            rsa_pubkey = rsa.PublicKey(mm, ee)
            crypto = self._encrypt(message.encode(), rsa_pubkey)
            return crypto.hex()
        except:
            return 'null'

    def _pad_for_encryption(self, message, target_length):
        message = message[::-1]
        max_msglength = target_length - 11
        msglength = len(message)

        padding = b''
        padding_length = target_length - msglength - 3

        for i in range(padding_length):
            padding += b'\x00'

        return b''.join([b'\x00\x00', padding, b'\x00', message])

    def _encrypt(self, message, pub_key):
        keylength = rsa.common.byte_size(pub_key.n)
        padded = self._pad_for_encryption(message, keylength)

        payload = rsa.transform.bytes2int(padded)
        encrypted = rsa.core.encrypt_int(payload, pub_key.e, pub_key.n)
        block = rsa.transform.int2bytes(encrypted, keylength)
        return block


class MData():
    def __init__(self, data=b"", characterSet='utf-8'):
        # data肯定为bytes
        self.data = data
        self.characterSet = characterSet

    def saveData(self, FileName):
        with open(FileName, 'wb') as f:
            f.write(self.data)

    def fromString(self, data):
        self.data = data.encode(self.characterSet)
        return self.data

    def fromBase64(self, data):
        self.data = base64.b64decode(data.encode(self.characterSet))
        return self.data

    def fromHexStr(self, data):
        self.data = binascii.a2b_hex(data)
        return self.data

    def toString(self):
        return self.data.decode(self.characterSet)

    def toBase64(self):
        try:
            return base64.b64encode(self.data).decode()
        except:
            return None

    def toHexStr(self):
        return binascii.b2a_hex(self.data).decode()

    def toBytes(self):
        return self.data

    def __str__(self):
        try:
            return self.toString()
        except Exception:
            return self.toBase64()


class AEScryptor():
    def __init__(self, key, mode, iv='', paddingMode="NoPadding", characterSet="utf-8"):
        '''
        构建一个AES对象
        key: 秘钥，字节型数据
        mode: 使用模式，只提供两种，AES.MODE_CBC, AES.MODE_ECB
        iv： iv偏移量，字节型数据
        paddingMode: 填充模式，默认为NoPadding, 可选NoPadding，ZeroPadding，PKCS5Padding，PKCS7Padding
        characterSet: 字符集编码
        '''
        self.key = key.encode('utf-8')
        self.mode = mode
        self.iv = iv.encode('utf-8')
        self.characterSet = characterSet
        self.paddingMode = paddingMode
        self.data = ""

    def __ZeroPadding(self, data):
        data += b'\x00'
        while len(data) % 16 != 0:
            data += b'\x00'
        return data

    def __StripZeroPadding(self, data):
        data = data[:-1]
        while len(data) % 16 != 0:
            data = data.rstrip(b'\x00')
            if data[-1] != b"\x00":
                break
        return data

    def __PKCS5_7Padding(self, data):
        needSize = 16 - len(data) % 16
        if needSize == 0:
            needSize = 16
        return data + needSize.to_bytes(1, 'little') * needSize

    def __StripPKCS5_7Padding(self, data):
        paddingSize = data[-1]
        return data.rstrip(paddingSize.to_bytes(1, 'little'))

    def __paddingData(self, data):
        if self.paddingMode == "NoPadding":
            if len(data) % 16 == 0:
                return data
            else:
                return self.__ZeroPadding(data)
        elif self.paddingMode == "ZeroPadding":
            return self.__ZeroPadding(data)
        elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":
            return self.__PKCS5_7Padding(data)
        else:
            print("不支持Padding")

    def __stripPaddingData(self, data):
        if self.paddingMode == "NoPadding":
            return self.__StripZeroPadding(data)
        elif self.paddingMode == "ZeroPadding":
            return self.__StripZeroPadding(data)

        elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":
            return self.__StripPKCS5_7Padding(data)
        else:
            print("不支持Padding")

    def setCharacterSet(self, characterSet):
        '''
        设置字符集编码
        characterSet: 字符集编码
        '''
        self.characterSet = characterSet

    def setPaddingMode(self, mode):
        '''
        设置填充模式
        mode: 可选NoPadding，ZeroPadding，PKCS5Padding，PKCS7Padding
        '''
        self.paddingMode = mode

    @retry()
    def decryptFromBase64(self, entext):
        '''
        从base64编码字符串编码进行AES解密
        entext: 数据类型str
        '''
        mData = MData(characterSet=self.characterSet)
        self.data = mData.fromBase64(entext)
        return self.__decrypt()

    def decryptFromHexStr(self, entext):
        '''
        从hexstr编码字符串编码进行AES解密
        entext: 数据类型str
        '''
        mData = MData(characterSet=self.characterSet)
        self.data = mData.fromHexStr(entext)
        return self.__decrypt()

    def decryptFromString(self, entext):
        '''
        从字符串进行AES解密
        entext: 数据类型str
        '''
        mData = MData(characterSet=self.characterSet)
        self.data = mData.fromString(entext)
        return self.__decrypt()

    def decryptFromBytes(self, entext):
        '''
        从二进制进行AES解密
        entext: 数据类型bytes
        '''
        self.data = entext
        return self.__decrypt()

    def encryptFromString(self, data):
        '''
        对字符串进行AES加密
        data: 待加密字符串，数据类型为str
        '''
        self.data = data.encode(self.characterSet)
        return self.__encrypt()

    def __encrypt(self):
        if self.mode == AES.MODE_CBC:
            aes = AES.new(self.key, self.mode, self.iv)
        elif self.mode == AES.MODE_ECB:
            aes = AES.new(self.key, self.mode)
        else:
            print("不支持这种模式")
            return

        data = self.__paddingData(self.data)
        enData = aes.encrypt(data)
        return MData(enData)

    def __decrypt(self):
        if self.mode == AES.MODE_CBC:
            aes = AES.new(self.key, self.mode, self.iv)
        elif self.mode == AES.MODE_ECB:
            aes = AES.new(self.key, self.mode)
        else:
            print("不支持这种模式")
            return
        data = aes.decrypt(self.data)
        mData = MData(self.__stripPaddingData(data),
                      characterSet=self.characterSet)
        return mData


def AesPr(key, mode, iv, paddingMode, characterSet, func=None, plaintext=None):
    try:
        if func == 'enc':
            return AEScryptor(key, mode, iv, paddingMode,
                              characterSet=characterSet).encryptFromString(plaintext).toBase64()
        if func == 'dec':
            return AEScryptor(key, mode, iv, paddingMode,
                              characterSet=characterSet).decryptFromBase64(plaintext).toString()
    except:
        return None


def encode(**kwargs):
    iv = kwargs.get("iv")
    key = kwargs.get("key")
    plaintext = kwargs.get("text")
    timesm = str(int(time.time() * 1000))
    Item = {
        "MD5": (md5_hash(plaintext)),
        "BASE64": (base64_encode(plaintext)),
        "HMAC": {
            "HMAC Key": key,
            "HMAC MD5": (hmac_md5(key, plaintext)),
            "HMAC SHA1": (hmac_encrypt(key, plaintext, sha_type="sha1")),
            "HMAC SHA3": (hmac_encrypt(key, plaintext, sha_type="sha3")),
            "HMAC SHA224": (hmac_encrypt(key, plaintext, sha_type="sha224")),
            "HMAC SHA256": (hmac_encrypt(key, plaintext, sha_type="sha256")),
            "HMAC SHA384": (hmac_encrypt(key, plaintext, sha_type="sha384")),
            "HMAC SHA512": (hmac_encrypt(key, plaintext, sha_type="sha512")),
        },
        "UNICODE": (unicode(plaintext)),
        "URL编码": (url_enquote(plaintext)),
        "SHA": {
            "128": (sha1(plaintext)),
            "256": (sha256(plaintext)),
            "512": (sha512(plaintext)),
        },
        "AES": {
            "iv": iv,
            "key": key,
            "CBC": {
                #  填充模式，默认为NoPadding, 可选NoPadding，ZeroPadding，PKCS5Padding，PKCS7Padding
                "pkcs7": AesPr(key, AES.MODE_CBC, iv, paddingMode="PKCS7Padding",
                               characterSet='utf-8', func='enc', plaintext=plaintext),
                "zeropadding": AesPr(key, AES.MODE_CBC, iv, paddingMode="ZeroPadding",
                                     characterSet='utf-8', func='enc', plaintext=plaintext),
            },
            "ECB": {
                "pkcs7": AesPr(key, AES.MODE_ECB, iv='', paddingMode="PKCS7Padding",
                               characterSet='utf-8', func='enc', plaintext=plaintext),
                "zeropadding": AesPr(key, AES.MODE_ECB, iv='', paddingMode="ZeroPadding",
                                     characterSet='utf-8', func='enc', plaintext=plaintext),
            }

        },
        "RSA-Model": Encrypt(iv, key).encrypt(plaintext),
        "Timestamp": {
            "Self": timesm,
            "Md5": md5_hash(timesm),
            "Base64": base64_encode(timesm)
        }
    }
    return Item


def decode(**kwargs):
    iv = kwargs.get("iv")
    key = kwargs.get("key")
    plaintext = kwargs.get("text")
    timesp = d_timestamp(plaintext)
    Item = {
        "BASE64": base64_decode(plaintext),
        "UNICODE": unicode_escape(plaintext),
        "URL编码": url_unquote(plaintext),
        "AES": {
            "iv": iv,
            "key": key,
            "CBC": {
                "pkcs7": AesPr(key=key, mode=AES.MODE_CBC, iv=iv, paddingMode="PKCS7Padding",
                               characterSet='utf-8', func='dec', plaintext=plaintext),
                "zeropadding": AesPr(key=key, mode=AES.MODE_CBC, iv=iv, paddingMode="ZeroPadding",
                                     characterSet='utf-8', func='dec', plaintext=plaintext),
            },
            "ECB": {
                "pkcs7": AesPr(key=key, mode=AES.MODE_ECB, iv='', paddingMode="PKCS7Padding",
                               characterSet='utf-8', func='dec', plaintext=plaintext),
                "zeropadding": AesPr(key=key, mode=AES.MODE_ECB, iv='', paddingMode="ZeroPadding",
                                     characterSet='utf-8', func='dec', plaintext=plaintext),
            },

        },
        "Timestamp": {
            "Self": timesp,
            "Md5": md5_hash(timesp),
            "Base64": base64_encode(timesp)
        }
    }
    return Item


if __name__ == '__main__':
    # 128位密钥（16字节）
    key = '1111111111111111'
    iv = ''
    plaintext = "wjYlaA/cRs7IbnbVZc9RRA=="
    k = {'key': '1111111111111111', 'iv': '',
         'text': 'wjYlaA/cRs7IbnbVZc9RRA=='}
    b = decode(**k)
    print(b)
